﻿using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace FastDFS.Client
{
    public class TrackerClient
    {
        protected TrackerGroup TrackerGroup;
        protected byte Errno;

        public TrackerClient()
        {
            this.TrackerGroup = ClientGlobal.TrackerGroup;
        }

        public TrackerClient(TrackerGroup trackerGroup)
        {
            this.TrackerGroup = trackerGroup;
        }

        public byte ErrorCode
        {
            get
            {
                return this.Errno;
            }
        }
        /// <summary>
        /// get a connection to tracker server
        /// </summary>
        /// <returns>tracker server Socket object</returns>
        public async Task<TrackerServer> GetConnectionAsync()
        {
            return await this.TrackerGroup.GetConnectionAsync();
        }
        /// <summary>
        /// query storage server to upload file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <returns>storage server Socket object</returns>
        public async Task<StorageServer> GetStoreStorageAsync(TrackerServer trackerServer)
        {
            return await GetStoreStorageAsync(trackerServer, null);
        }
        /// <summary>
        /// query storage server to upload file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <returns>storage server object</returns>
        public async Task<StorageServer> GetStoreStorageAsync(TrackerServer trackerServer, string groupName)
        {
            byte[] header;
            string ipAddr;
            int port;
            byte cmd;
            int outLen;
            bool bNewConnection;
            byte storePath;
            TcpClient trackerSocket;

            if (trackerServer == null)
            {
                trackerServer =await GetConnectionAsync();
                bNewConnection = true;
            }
            else
            {
                bNewConnection = false;
            }

            trackerSocket =await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            try
            {
                if (string.IsNullOrEmpty(groupName))
                {
                    cmd = ProtoCommon.TrackerProtoCmdServiceQueryStoreWithoutGroupOne;
                    outLen = 0;
                }
                else
                {
                    cmd = ProtoCommon.TrackerProtoCmdServiceQueryStoreWithGroupOne;
                    outLen = ProtoCommon.FdfsGroupNameMaxLen;
                }
                header = ProtoCommon.PackHeader(cmd, outLen, (byte)0);
                output.Write(header, 0, header.Length);

                if (!string.IsNullOrEmpty(groupName))
                {
                    byte[] bGroupName;
                    byte[] bs;
                    int groupLen;

                    bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                    bGroupName = new byte[ProtoCommon.FdfsGroupNameMaxLen];

                    if (bs.Length <= ProtoCommon.FdfsGroupNameMaxLen)
                    {
                        groupLen = bs.Length;
                    }
                    else
                    {
                        groupLen = ProtoCommon.FdfsGroupNameMaxLen;
                    }
                    for (int i = 0; i < bGroupName.Length; i++)
                        bGroupName[i] = (byte)0;
                    Array.Copy(bs, 0, bGroupName, 0, groupLen);
                    output.Write(bGroupName, 0, bGroupName.Length);
                }

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                             ProtoCommon.TrackerProtoCmdResp,
                                             ProtoCommon.TrackerQueryStorageStoreBodyLen);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException($"get storage server received error: {pkgInfo.Errno}");
                }

                ipAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, ProtoCommon.FdfsGroupNameMaxLen, ProtoCommon.FdfsIpaddrSize - 1).Replace("\0", "").Trim();

                port = (int)ProtoCommon.Buff2Long(pkgInfo.Body, ProtoCommon.FdfsGroupNameMaxLen
                                + ProtoCommon.FdfsIpaddrSize - 1);
                storePath = pkgInfo.Body[ProtoCommon.TrackerQueryStorageStoreBodyLen - 1];

                return new StorageServer(ipAddr, port, storePath);
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {
 
                    }
                }
            }
        }
        /// <summary>
        /// query storage servers to upload file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <returns>storage servers</returns>
        public async Task<StorageServer[]> GetStoreStoragesAsync(TrackerServer trackerServer, string groupName)
        {
            byte[] header;
            string ipAddr;
            int port;
            byte cmd;
            int outLen;
            bool bNewConnection;
            TcpClient trackerSocket;

            if (trackerServer == null)
            {
                trackerServer = await GetConnectionAsync();
                bNewConnection = true;
            }
            else
            {
                bNewConnection = false;
            }

            trackerSocket =await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            try
            {
                if (string.IsNullOrEmpty(groupName))
                {
                    cmd = ProtoCommon.TrackerProtoCmdServiceQueryStoreWithoutGroupAll;
                    outLen = 0;
                }
                else
                {
                    cmd = ProtoCommon.TrackerProtoCmdServiceQueryStoreWithGroupAll;
                    outLen = ProtoCommon.FdfsGroupNameMaxLen;
                }
                header = ProtoCommon.PackHeader(cmd, outLen, (byte)0);
                output.Write(header, 0, header.Length);

                if (!string.IsNullOrEmpty(groupName))
                {
                    byte[] bGroupName;
                    byte[] bs;
                    int groupLen;

                    bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                    bGroupName = new byte[ProtoCommon.FdfsGroupNameMaxLen];

                    if (bs.Length <= ProtoCommon.FdfsGroupNameMaxLen)
                    {
                        groupLen = bs.Length;
                    }
                    else
                    {
                        groupLen = ProtoCommon.FdfsGroupNameMaxLen;
                    }
                    for (int i = 0; i < bGroupName.Length; i++)
                        bGroupName[i] = (byte)0;
                    Array.Copy(bs, 0, bGroupName, 0, groupLen);
                    output.Write(bGroupName, 0, bGroupName.Length);
                }

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                             ProtoCommon.TrackerProtoCmdResp, -1);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException($"TrackerProtoCmdResp received error: {pkgInfo.Errno}");
                }

                if (pkgInfo.Body.Length < ProtoCommon.TrackerQueryStorageStoreBodyLen)
                {
                    this.Errno = ProtoCommon.ErrNoEinval;
                    throw new FdfsException($"TrackerProtoCmdResp error:pkgInfo.Body.Length({pkgInfo.Body.Length}) <{ProtoCommon.TrackerQueryStorageStoreBodyLen}");
                }

                int ipPortLen = pkgInfo.Body.Length - (ProtoCommon.FdfsGroupNameMaxLen + 1);
                int recordLength = ProtoCommon.FdfsIpaddrSize - 1 + ProtoCommon.FdfsProtoPkgLenSize;

                if (ipPortLen % recordLength != 0)
                {
                    this.Errno = ProtoCommon.ErrNoEinval;
                    throw new FdfsException($"TrackerProtoCmdResp error ipPortLen % recordLength != 0: {ProtoCommon.ErrNoEinval}");
                }

                int serverCount = ipPortLen / recordLength;
                if (serverCount > 16)
                {
                    this.Errno = ProtoCommon.ErrNoEnospc;
                    throw new FdfsException($"TrackerProtoCmdResp error ipPortLen / recordLength>16: {ProtoCommon.ErrNoEinval}");
                }

                StorageServer[] results = new StorageServer[serverCount];
                byte storePath = pkgInfo.Body[pkgInfo.Body.Length - 1];
                int offset = ProtoCommon.FdfsGroupNameMaxLen;

                for (int i = 0; i < serverCount; i++)
                {
                    ipAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, offset, ProtoCommon.FdfsIpaddrSize - 1).Replace("\0", "").Replace("\0", "").Trim();
                    offset += ProtoCommon.FdfsIpaddrSize - 1;

                    port = (int)ProtoCommon.Buff2Long(pkgInfo.Body, offset);
                    offset += ProtoCommon.FdfsProtoPkgLenSize;

                    results[i] = new StorageServer(ipAddr, port, storePath);
                }

                return results;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }
            }
        }
        /// <summary>
        /// query storage server to download file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="filename">filename on storage server</param>
        /// <returns>storage server Socket object</returns>
        public async Task<StorageServer> GetFetchStorageAsync(TrackerServer trackerServer, string groupName, string filename)
        {
            ServerInfo[] servers = await this.GetStoragesAsync(trackerServer, ProtoCommon.TrackerProtoCmdServiceQueryFetchOne, groupName, filename);
            return  new StorageServer(servers[0].IpAddr, servers[0].Port, 0);
        }
        /// <summary>
        /// query storage server to update file (delete file or set meta data)
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="filename">filename on storage server</param>
        /// <returns>storage server Socket object</returns>
        public async Task<StorageServer> GetUpdateStorageAsync(TrackerServer trackerServer, string groupName, string filename)
        {
            ServerInfo[] servers = await this.GetStoragesAsync(trackerServer, ProtoCommon.TrackerProtoCmdServiceQueryUpdate,
                    groupName, filename);
            return  new StorageServer(servers[0].IpAddr, servers[0].Port, 0);
        }
        /// <summary>
        /// get storage servers to download file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="filename">filename on storage server</param>
        /// <returns>storage servers</returns>
        public async Task<ServerInfo[]> GetFetchStoragesAsync(TrackerServer trackerServer, string groupName, string filename)
        {
            return await this.GetStoragesAsync(trackerServer, ProtoCommon.TrackerProtoCmdServiceQueryFetchAll,
                    groupName, filename);
        }
        /// <summary>
        /// query storage server to download file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="cmd">command code, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE or ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="filename">filename on storage server</param>
        /// <returns>storage server Socket object</returns>
        protected async Task<ServerInfo[]> GetStoragesAsync(TrackerServer trackerServer, byte cmd, string groupName, string filename)
        {
            byte[] header;
            byte[] bFileName;
            byte[] bGroupName;
            byte[] bs;
            int len;
            string ipAddr;
            int port;
            bool bNewConnection;
            TcpClient trackerSocket;

            if (trackerServer == null)
            {
                trackerServer = await GetConnectionAsync();
                bNewConnection = true;
            }
            else
            {
                bNewConnection = false;
            }
            trackerSocket =await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            try
            {
                bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                bGroupName = new byte[ProtoCommon.FdfsGroupNameMaxLen];
                bFileName = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(filename);

                if (bs.Length <= ProtoCommon.FdfsGroupNameMaxLen)
                {
                    len = bs.Length;
                }
                else
                {
                    len = ProtoCommon.FdfsGroupNameMaxLen;
                }
                for (int i = 0; i < bGroupName.Length; i++)
                    bGroupName[i] = (byte)0;
                Array.Copy(bs, 0, bGroupName, 0, len);

                header = ProtoCommon.PackHeader(cmd, ProtoCommon.FdfsGroupNameMaxLen + bFileName.Length, (byte)0);
                byte[] wholePkg = new byte[header.Length + bGroupName.Length + bFileName.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(bGroupName, 0, wholePkg, header.Length, bGroupName.Length);
                Array.Copy(bFileName, 0, wholePkg, header.Length + bGroupName.Length, bFileName.Length);
                output.Write(wholePkg, 0, wholePkg.Length);

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                             ProtoCommon.TrackerProtoCmdResp, -1);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException($" GetStoragesAsync error: {pkgInfo.Errno}");
                }

                if (pkgInfo.Body.Length < ProtoCommon.TrackerQueryStorageFetchBodyLen)
                {
                    throw new IOException("Invalid body length: " + pkgInfo.Body.Length);
                }

                if ((pkgInfo.Body.Length - ProtoCommon.TrackerQueryStorageFetchBodyLen) % (ProtoCommon.FdfsIpaddrSize - 1) != 0)
                {
                    throw new IOException("Invalid body length: " + pkgInfo.Body.Length);
                }

                int serverCount = 1 + (pkgInfo.Body.Length - ProtoCommon.TrackerQueryStorageFetchBodyLen) / (ProtoCommon.FdfsIpaddrSize - 1);

                ipAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, ProtoCommon.FdfsGroupNameMaxLen, ProtoCommon.FdfsIpaddrSize - 1).Replace("\0", "").Trim();
                int offset = ProtoCommon.FdfsGroupNameMaxLen + ProtoCommon.FdfsIpaddrSize - 1;

                port = (int)ProtoCommon.Buff2Long(pkgInfo.Body, offset);
                offset += ProtoCommon.FdfsProtoPkgLenSize;

                ServerInfo[] servers = new ServerInfo[serverCount];
                servers[0] = new ServerInfo(ipAddr, port);
                for (int i = 1; i < serverCount; i++)
                {
                    servers[i] = new ServerInfo(Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, offset, ProtoCommon.FdfsIpaddrSize - 1).Replace("\0", "").Trim(), port);
                    offset += ProtoCommon.FdfsIpaddrSize - 1;
                }

                return servers;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }
            }
        }
        /// <summary>
        /// query storage server to download file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="fileId">the file id(including group name and filename)</param>
        /// <returns>storage server Socket object</returns>
        public async Task<StorageServer> GetFetchStorage1Async(TrackerServer trackerServer, string fileId)
        {
            string[] parts = new string[2];
            this.Errno = StorageClientEx.SplitFileId(fileId, parts);
            if (this.Errno != 0)
            {
                throw new FdfsException($"StorageClientEx.SplitFileId error: {this.Errno}");
            }

            return await this.GetFetchStorageAsync(trackerServer, parts[0], parts[1]);
        }
        /// <summary>
        /// get storage servers to download file
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="fileId">the file id(including group name and filename)</param>
        /// <returns>storage servers</returns>
        public async Task<ServerInfo[]> GetFetchStorages1Async(TrackerServer trackerServer, string fileId)
        {
            string[] parts = new string[2];
            this.Errno = StorageClientEx.SplitFileId(fileId, parts);
            if (this.Errno != 0)
            {
                throw new FdfsException($"StorageClientEx.SplitFileId error: {this.Errno}");
            }

            return await this.GetFetchStoragesAsync(trackerServer, parts[0], parts[1]);
        }
        /// <summary>
        /// list groups
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <returns>group stat array</returns>
        public async Task<StructGroupStat[]> ListGroupsAsync(TrackerServer trackerServer)
        {
            byte[] header;;
            bool bNewConnection;
            TcpClient trackerSocket;

            if (trackerServer == null)
            {
                trackerServer = await GetConnectionAsync();
                bNewConnection = true;
            }
            else
            {
                bNewConnection = false;
            }

            trackerSocket =await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            try
            {
                header = ProtoCommon.PackHeader(ProtoCommon.TrackerProtoCmdServerListGroup, 0, (byte)0);
                output.Write(header, 0, header.Length);

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                             ProtoCommon.TrackerProtoCmdResp, -1);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException($"ListGroupsAsync error: {this.Errno}");
                }

                ProtoStructDecoder decoder = new ProtoStructDecoder();
                return decoder.Decode<StructGroupStat>(pkgInfo.Body, StructGroupStat.GetFieldsTotalSize());
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }

                throw;
            }
            catch (Exception)
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }
            }
        }
        /// <summary>
        /// query storage server stat info of the group
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <returns>storage server stat array</returns>
        public async Task<StructStorageStat[]> ListStoragesAsync(TrackerServer trackerServer, string groupName)
        {
            return await this.ListStoragesAsync(trackerServer, groupName, null);
        }
        /// <summary>
        /// query storage server stat info of the group
        /// </summary>
        /// <param name="trackerServer">the tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="storageIpAddr">the storage server ip address, can be null or empty</param>
        /// <returns>storage server stat array</returns>
        public async Task<StructStorageStat[]> ListStoragesAsync(TrackerServer trackerServer, string groupName, string storageIpAddr)
        {
            byte[] header;
            byte[] bGroupName;
            byte[] bs;
            int len;
            bool bNewConnection;
            TcpClient trackerSocket;

            if (trackerServer == null)
            {
                trackerServer = await GetConnectionAsync();
                bNewConnection = true;
            }
            else
            {
                bNewConnection = false;
            }
            trackerSocket =await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            try
            {
                bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                bGroupName = new byte[ProtoCommon.FdfsGroupNameMaxLen];

                if (bs.Length <= ProtoCommon.FdfsGroupNameMaxLen)
                {
                    len = bs.Length;
                }
                else
                {
                    len = ProtoCommon.FdfsGroupNameMaxLen;
                }
                for (int i = 0; i < bGroupName.Length; i++)
                    bGroupName[i] = (byte)0;
                Array.Copy(bs, 0, bGroupName, 0, len);

                int ipAddrLen;
                byte[] bIpAddr;
                if (!string.IsNullOrEmpty(storageIpAddr))
                {
                    bIpAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(storageIpAddr);
                    if (bIpAddr.Length < ProtoCommon.FdfsIpaddrSize)
                    {
                        ipAddrLen = bIpAddr.Length;
                    }
                    else
                    {
                        ipAddrLen = ProtoCommon.FdfsIpaddrSize - 1;
                    }
                }
                else
                {
                    bIpAddr = null;
                    ipAddrLen = 0;
                }

                header = ProtoCommon.PackHeader(ProtoCommon.TrackerProtoCmdServerListStorage, ProtoCommon.FdfsGroupNameMaxLen + ipAddrLen, (byte)0);
                byte[] wholePkg = new byte[header.Length + bGroupName.Length + ipAddrLen];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(bGroupName, 0, wholePkg, header.Length, bGroupName.Length);
                if (ipAddrLen > 0)
                {
                    Array.Copy(bIpAddr, 0, wholePkg, header.Length + bGroupName.Length, ipAddrLen);
                }
                output.Write(wholePkg, 0, wholePkg.Length);

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                             ProtoCommon.TrackerProtoCmdResp, -1);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException($"ListStoragesAsync error: {this.Errno}");
                }

                ProtoStructDecoder decoder = new ProtoStructDecoder();
                return decoder.Decode<StructStorageStat>(pkgInfo.Body, StructStorageStat.GetFieldsTotalSize());
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }

                throw;
            }
            catch (Exception)
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                }
            }
        }
        /// <summary>
        /// delete a storage server from the tracker server
        /// </summary>
        /// <param name="trackerServer">the connected tracker server</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="storageIpAddr">the storage server ip address</param>
        /// <returns>true for success, false for fail</returns>
        private async Task<bool> DeleteStorageAsync(TrackerServer trackerServer, string groupName, string storageIpAddr)
        {
            byte[] header;
            byte[] bGroupName;
            byte[] bs;
            int len;
            TcpClient trackerSocket;

            trackerSocket = await trackerServer.GetSocketAsync();
            Stream output = trackerSocket.GetStream();

            bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
            bGroupName = new byte[ProtoCommon.FdfsGroupNameMaxLen];

            if (bs.Length <= ProtoCommon.FdfsGroupNameMaxLen)
            {
                len = bs.Length;
            }
            else
            {
                len = ProtoCommon.FdfsGroupNameMaxLen;
            }
            for (int i = 0; i < bGroupName.Length; i++)
                bGroupName[i] = (byte)0;
            Array.Copy(bs, 0, bGroupName, 0, len);

            int ipAddrLen;
            byte[] bIpAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(storageIpAddr);
            if (bIpAddr.Length < ProtoCommon.FdfsIpaddrSize)
            {
                ipAddrLen = bIpAddr.Length;
            }
            else
            {
                ipAddrLen = ProtoCommon.FdfsIpaddrSize - 1;
            }

            header = ProtoCommon.PackHeader(ProtoCommon.TrackerProtoCmdServerDeleteStorage, ProtoCommon.FdfsGroupNameMaxLen + ipAddrLen, (byte)0);
            byte[] wholePkg = new byte[header.Length + bGroupName.Length + ipAddrLen];
            Array.Copy(header, 0, wholePkg, 0, header.Length);
            Array.Copy(bGroupName, 0, wholePkg, header.Length, bGroupName.Length);
            Array.Copy(bIpAddr, 0, wholePkg, header.Length + bGroupName.Length, ipAddrLen);
            output.Write(wholePkg, 0, wholePkg.Length);

            ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(trackerSocket.GetStream(),
                                         ProtoCommon.TrackerProtoCmdResp, 0);
            this.Errno = pkgInfo.Errno;
            return pkgInfo.Errno == 0;
        }
        /// <summary>
        /// delete a storage server from the global FastDFS cluster
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="storageIpAddr">the storage server ip address</param>
        /// <returns>true for success, false for fail</returns>
        public async Task<bool> DeleteStorageAsync(string groupName, string storageIpAddr)
        {
            return await this.DeleteStorageAsync(ClientGlobal.TrackerGroup, groupName, storageIpAddr);
        }
        /// <summary>
        /// delete a storage server from the FastDFS cluster
        /// </summary>
        /// <param name="trackerGroup">the tracker server group</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="storageIpAddr">the storage server ip address</param>
        /// <returns>true for success, false for fail</returns>
        public async Task<bool> DeleteStorageAsync(TrackerGroup trackerGroup, string groupName, string storageIpAddr)
        {
            int serverIndex;
            int notFoundCount;
            TrackerServer trackerServer;

            notFoundCount = 0;
            for (serverIndex = 0; serverIndex < trackerGroup.TrackerServers.Length; serverIndex++)
            {
                try
                {
                    trackerServer = await trackerGroup.GetConnectionAsync();
                }
                catch (IOException)
                {
                    this.Errno = ProtoCommon.Econnrefused;
                    return false;
                }

                try
                {
                    StructStorageStat[] storageStats =await ListStoragesAsync(trackerServer, groupName, storageIpAddr);
                    if (storageStats == null)
                    {
                        if (this.Errno == ProtoCommon.ErrNoEnoent)
                        {
                            notFoundCount++;
                        }
                        else
                        {
                            return false;
                        }
                    }
                    else if (storageStats.Length == 0)
                    {
                        notFoundCount++;
                    }
                    else if (storageStats[0].Status == ProtoCommon.FdfsStorageStatusOnline ||
                             storageStats[0].Status == ProtoCommon.FdfsStorageStatusActive)
                    {
                        this.Errno = ProtoCommon.ErrNoEbusy;
                        return false;
                    }
                }
                finally
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch
                    {
                        // ignore
                    }
                }
            }

            if (notFoundCount == trackerGroup.TrackerServers.Length)
            {
                this.Errno = ProtoCommon.ErrNoEnoent;
                return false;
            }

            notFoundCount = 0;
            for (serverIndex = 0; serverIndex < trackerGroup.TrackerServers.Length; serverIndex++)
            {
                try
                {
                    trackerServer =await trackerGroup.GetConnectionAsync();
                }
                catch (IOException)
                {
                    Console.WriteLine("connect to server " + trackerGroup.TrackerServers[serverIndex].Address + ":" + trackerGroup.TrackerServers[serverIndex].Port + " fail");
                    this.Errno = ProtoCommon.Econnrefused;
                    return false;
                }

                try
                {
                    if (! (await DeleteStorageAsync(trackerServer, groupName, storageIpAddr)))
                    {
                        if (this.Errno != 0)
                        {
                            if (this.Errno == ProtoCommon.ErrNoEnoent)
                            {
                                notFoundCount++;
                            }
                            else if (this.Errno != ProtoCommon.ErrNoEalready)
                            {
                                return false;
                            }
                        }
                    }
                }
                finally
                {
                    try
                    {
                        trackerServer.Close();
                    }
                    catch 
                    {
                        // ignore
                    }
                }
            }

            if (notFoundCount == trackerGroup.TrackerServers.Length)
            {
                this.Errno = ProtoCommon.ErrNoEnoent;
                return false;
            }

            if (this.Errno == ProtoCommon.ErrNoEnoent)
            {
                this.Errno = 0;
            }

            return this.Errno == 0;
        }
    }
}
